package medOffice;

import java.sql.SQLException;
import java.sql.Time;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.List;

import medOffice.Enuns.TipoDeConsulta;
import medOffice.Enuns.Urgencia;
import medOffice.dao.ConsultaDAO;

/**
 * Classe que representa uma Agenda
 * 
 * @author Jose Amilton Filho - amiltonacioli@ibest.com.br
 * @version 1.0 - 04/12/2009
 */
public class Agenda{

	protected ConsultaDAO consultaDAO;
	private Medico donoAgenda;

	/**
	 * Construtor da Agenda
	 * 
	 * @param Medico
	 *            O medico dono da Agenda
	 * @throws Exception
	 *             caso o parametro seja <code>null</code>
	 */
	public Agenda(Medico medico) throws Exception {
		setDonoAgenda(medico);
		consultaDAO = new ConsultaDAO();
	}



	/**
	 * Metodo acessador que retorna o medico dono
	 * da agenda.
	 * @return Medico dono da Agenda
	 */
	public Medico getDonoAgenda() {
		return donoAgenda;
	}

	/**
	 * Metodo modificador que seta o Medico dono
	 * da agenda.
	 * 
	 * @param Medico donoAgenda
	 *            o novo Medico dono da Agenda
	 * @throws Exception
	 *             caso o parametro seja <code>null</code>
	 */
	public void setDonoAgenda(Medico donoAgenda) throws Exception {
		if (donoAgenda == null)
			throw new Exception("Medico invalido");
		this.donoAgenda = donoAgenda;
	}

	/**
	 * Metodo que marca uma {@link Consulta} na Agenda
	 * 
	 * @param Consulta
	 *            A consulta a ser marcada
	 * @return boolean True se foi marcada com sucesso, False caso contrario
	 * @throws Exception
	 *             lancada caso o parametro seja <code>null</code>
	 * 
	 */
	public boolean marcaConsulta(Consulta consulta) throws Exception {
		if(consulta == null)
			throw new Exception("Consulta null");
		
		if (!(validaConsulta(consulta)))
			return false;
				
		try{
			getConsultaDAO().create(consulta);
		}catch(SQLException exq){
			return false;
		}
		return true;

	}

	/**
	 * Metodo que cancela uma {@link Consulta} removendo a mesma da lista
	 * de consultas e da lista de urgencias, caso ela seja urgente.
	 * 
	 * @param {@link Consulta} 
	 *            A consulta que vai ser cancelada
	 * @return boolean True se for removida com sucesso, False caso contrario
	 * @throws Exception
	 *             caso o parametro seja <code>null</code>
	 */
	public boolean cancelaConsulta(Consulta consulta) throws Exception {
		if (consulta == null)
			throw new Exception("parametro consulta null");

		if (getListaDeConsultas().contains(consulta)) {
			getConsultaDAO().delete(consulta);
			return true;
		}
		return false;
	}
	
	/**
	 * Metodo que cancela uma consulta passando como parametro um
	 * dado cpf do cliente.
	 * 
	 * @param String cpf do paciente
	 * @throws Exception
	 * 				lancada caso o cpf passado seja nulo.
	 */
	public void cancelaConsultaCPF(String cpf) throws Exception {
		if (cpf == null) {
			throw new Exception("parametro cpf null");	
		}
		getConsultaDAO().delete(cpf);		
	}
	
	
	/**
	 * Metodo que cancela todas as consultas de um determinado
	 * dia passado como parametro
	 * 
	 * @param Calendar dia desejado.
	 * @throws Exception
	 * 				lancada caso o dia passado seja nulo.
	 */
	public void cancelaConsultaDia(Calendar dia) throws Exception {
		if(dia == null){
			throw new Exception("parametro dia null");
		}		
		getConsultaDAO().deleteConsultasDoDia(dia);		
	}
	
	
	/**
	 * Metodo que retorna uma lista de Consultas do {@link Medico}
	 * 
	 * @return List<Consulta> lista de Consultas do Medico dono da Agenda
	 * @throws SQLException 
	 */
	public List<Consulta> getListaDeConsultas() throws SQLException {
		return getConsultaDAO().getListaConsultas();
	}

	/**
	 * Metodo que retorna a lista de urgencias do {@link Medico}
	 * 
	 * @return List<Consulta> lista de Urgencia do Medico dono da Agenda
	 * @throws SQLException 
	 */
	public List<Consulta> listaDeUrgencia() throws SQLException {
		return getConsultaDAO().listaConsultasPorUrgencia(Urgencia.S);
	}

	/**
	 * Metodo que retorna a lista de consultas de um determinado dia passado
	 * como parametro. Recebe um calendar que contem a data do dia
	 * 
	 * @param Calendar
	 *            O dia que tera suas Consultas pesquisadas
	 * @return List<consulta> consultas do Dia
	 * @throws Exception
	 *             caso o dia seja <code>null</code>
	 */
	public List<Consulta> listaConsultas(Calendar dia) throws Exception {
		if (dia == null)
			throw new Exception("parametro dia null");

		if (!getDonoAgenda().getHorariosDisponiveis().containsKey(
				dia.get(Calendar.DAY_OF_WEEK)))
			throw new Exception("dia nao existe");
		
		return getConsultaDAO().listaConsultasPorDataDia(dia);
	}

	/**
	 * Metodo que sugere um dia disponivel para marcar a consulta retorna a
	 * sugestao do dia e retorna um horario disponivel a marcar.
	 * 
	 * @param {@link TipoDeConsulta}
	 *             tipo da consulta que se deseja sugestao de dia a ser marcada.
	 *             
	 * @return Calendar com proximo dia disponivel para aquele {@link TipoDeConsulta}
	 * 
	 * @throws Exception
	 *             caso o {@link TipoDeConsulta} seja <code>null</code>
	 *             
	 */
	public Calendar proximoDiaDisponivel(TipoDeConsulta tipo) throws Exception {
		if (tipo == null)
			throw new Exception("parametro tipo null");
		List<Time[]> horarios;
		Calendar diaPesquisa = new GregorianCalendar();
		Calendar checarHoraSegunda;
		
		HashMap<Integer, ArrayList<Integer>> diaHorario = getDonoAgenda()
				.calculaMinutosDiaHorario();
							
		while (true) {
			try {
				if(getDonoAgenda().getHorariosDisponiveis().containsKey(
						diaPesquisa.get(Calendar.DAY_OF_WEEK))
					&& (calculaTempoConsultas(listaConsultas(diaPesquisa)) 
							+ getDonoAgenda().getTempoMinutos(tipo) 
							<= somaArray(diaHorario
							.get(diaPesquisa.get(Calendar.DAY_OF_YEAR))))) {
						
						horarios = getDonoAgenda().getHorariosDisponiveis().get(
								diaPesquisa.get(Calendar.DAY_OF_WEEK));
						
						for(Time[] hora : horarios) {
							diaPesquisa = alteraHoraCalendar (((Calendar) diaPesquisa),
									hora[0]);
							checarHoraSegunda = alteraHoraCalendar(((Calendar) diaPesquisa.clone()),
									hora[1]);
							checarHoraSegunda.add(Calendar.MINUTE,
									-getDonoAgenda().getTempoMinutos(tipo));
							
							while(diaPesquisa.before(checarHoraSegunda)){
								Calendar proximoHorarioPesquisa = (Calendar) diaPesquisa.clone();
								proximoHorarioPesquisa.add(Calendar.MINUTE,
										getDonoAgenda().getTempoMinutos(tipo));
								List<Consulta> listaConflito =
									consultasEntreHorarios(diaPesquisa,proximoHorarioPesquisa);
								
								if(listaConflito.size() > 0){
									diaPesquisa = (Calendar) listaConflito.get(-1).getData().clone();
									diaPesquisa.add(Calendar.MINUTE,
											getDonoAgenda().getTempoMinutos(
													listaConflito.get(-1).getTipoDeConsulta()));
								}else{
									return diaPesquisa;
								}
								
							}
							
						}
					}
				
			} catch (Exception ex) {
			}
			diaPesquisa.add(Calendar.DAY_OF_YEAR, +1);
		}
	}

	/**
	 * Metodo que retorna a duracao em minutos de uma lista de consultas
	 * 
	 * @param List<Consulta>
	 *            Lista de Consultas de onde vai ser calculado a duracao em
	 *            minutos
	 * @return int duracao em minutos
	 */
	private int calculaTempoConsultas(List<Consulta> consultas) {
		int soma = 0;
		for (Consulta cons : consultas) {
			soma += getDonoAgenda().getTempoMinutos(cons.getTipoDeConsulta());
		}
		return soma;

	}

	/**
	 * Metodo que soma uma lista de Integer
	 * 
	 * @param List<Integer>
	 *            a lista de Integer que terao seus itens somados
	 * @return int resultado da soma
	 */
	private int somaArray(List<Integer> aSomar) {
		int soma = 0;
		for (Integer x : aSomar) {
			soma += x;
		}
		return soma;
	}

	/**
	 * Metodo que verifica se uma {@link Consulta} esta dentro dos horarios de
	 * atendimento do {@link Medico}
	 * 
	 * @param {@link Consulta}
	 *            a consulta que vai ser verificada
	 * @return boolean 
	 * 			  True caso a consulta esteja no intervalo de horarios de
	 * atendimento do Medico, False caso contrario.
	 */
	public boolean checarHorarioCerto(Consulta consulta) {
		Calendar checarHoraPrimeira;
		Calendar checarHoraSegunda;
		
		Calendar checarHoraFinal = (Calendar) consulta.getData().clone();
		checarHoraFinal.add(Calendar.MINUTE, 
				getDonoAgenda().getTempoMinutos(consulta.getTipoDeConsulta()));
		
		List<Time[]> horarios = getDonoAgenda().getHorariosDisponiveis().get(
				consulta.getData().get(Calendar.DAY_OF_WEEK));

		for (Time[] hora : horarios) {
			checarHoraPrimeira = alteraHoraCalendar (((Calendar) consulta.getData().clone()),
					hora[0]);
			checarHoraSegunda = alteraHoraCalendar(((Calendar) consulta.getData().clone()),
					hora[1]);
			
			if ((consulta.getData().after(checarHoraPrimeira) 
					|| consulta.getData().equals(checarHoraPrimeira))
					&& (checarHoraFinal.before(checarHoraSegunda) 
					|| checarHoraFinal.equals(checarHoraSegunda)))
				return true;
			
		}
		return false;
	}
	
	/**
	 * Metodo que muda a hora, minuto e segundo de um Calendar
	 * de acordo com o Time passado
	 * @param Calendar data 
	 * @param Time horario
	 * 
	 * @return Calendar data com os novos horarios.
	 */
	private Calendar alteraHoraCalendar(Calendar dataMudar, Time horario){
		Calendar atual = new GregorianCalendar();
		atual.setTimeInMillis(horario.getTime());
		dataMudar.set(Calendar.HOUR_OF_DAY, 
				atual.get(Calendar.HOUR_OF_DAY));
		dataMudar.set(Calendar.MINUTE, 
				atual.get(Calendar.MINUTE));
		dataMudar.set(Calendar.SECOND, 
				atual.get(Calendar.SECOND));
		dataMudar.set(Calendar.MILLISECOND, 0);
		
		return dataMudar;
	}
	
	/**
	 * Metodo que valida uma {@link Consulta}
	 * 
	 * @param {@link Consulta}
	 *            consulta que vai ser verificada
	 * @return False se a {@link Consulta} for invalida (<code>null</code>, nao
	 *         estiver em um dia disponivel do {@link Medico} ou no caso de ja
	 *         existir uma {@link Consulta} igual), True caso contrario
	 * @throws Exception
	 *             caso a {@link Consulta} passada como parametro seja
	 *             <code>null</code>
	 */
	private boolean validaConsulta(Consulta consulta) throws Exception {
		if (consulta == null)
			throw new Exception("parametro consulta null");
		
		if (!getDonoAgenda().getHorariosDisponiveis().containsKey(
				consulta.getData().get(Calendar.DAY_OF_WEEK)))
			return false;
		
		try{
			return checarHorario(consulta);
		}catch(Exception ex){
			return false;
		}
	}

	/**
	 * Metodo que retorna a lista de nomes dos Pacientes com Urgencia
	 * 
	 * @return List<String> Lista de nomes dos pacientes.
	 * @throws SQLException 
	 */
	public List<String> listaPacientesUrgentes() throws SQLException {
		List<String> nomes = new ArrayList<String>();
		for (Consulta cons : listaDeUrgencia())
			nomes.add(cons.getPaciente().getNome());
		return nomes;
	}

	/**
	 * Metodo para checar se o horario da Consulta esta disponivel
	 * 
	 * @param consulta
	 *            A {@link Conuslta} que vai ter o horario verificado
	 * @return <code>true</code> caso o horario esteja disponivel e
	 *         <code>false</code> caso contrario
	 * @throws Exception
	 *             caso a {@link Consulta} passada como parametro seja
	 *             <code>null</code>
	 */

	public boolean checarHorario(Consulta consulta) throws Exception {
		if (consulta == null)
			throw new Exception("Operacao invelida: Parametro null");
		return checarHorarioCerto(consulta) &&
				checarConflitoAntesHorario(consulta)
				&& checarCoflitoPosHorario(consulta);

	}

	/**
	 * Metodo que verifica se uma Consulta tem algum conflito com outra antes
	 * dela
	 * 
	 * @param consulta
	 *            a Consulta a ser verificada
	 * @return <code>true</code> caso nao haja conflito, e <code>false</code>
	 *         caso contrario
	 * @throws Exception
	 */
	private boolean checarConflitoAntesHorario(Consulta consulta)
			throws Exception {
		
		Calendar intervaloMaximo = (Calendar) consulta.getData().clone();
		intervaloMaximo.add(Calendar.MINUTE, -getDonoAgenda()
				.getMaxTempoMinutoConsulta());
		
		List<Consulta> consultasDoHorario = consultasEntreHorarios(
				intervaloMaximo, consulta.getData());
		
		Calendar dataParaCopia = null;

		for (Consulta cons : consultasDoHorario) {
			dataParaCopia = (Calendar) cons.getData().clone();
			dataParaCopia.add(Calendar.MINUTE, getDonoAgenda().getTempoMinutos(cons
					.getTipoDeConsulta()));
			if (consulta.getData().before(dataParaCopia))
				return false;
		}
		
		return true;
	}

	/**
	 * Metodo que verifica se uma Consulta entra em conflito com alguma depois
	 * dela
	 * 
	 * @param consulta
	 *            a Consulta a ser verificada
	 * @return <code>true</code> caso nao haja conflito e <code>false</code>
	 *         caso contrario
	 * @throws Exception
	 */
	private boolean checarCoflitoPosHorario(Consulta consulta) throws Exception {
		
		Calendar horarioFinalDaConsulta = (Calendar) consulta.getData().clone();
		horarioFinalDaConsulta.add(Calendar.MINUTE, getDonoAgenda()
				.getTempoMinutos(consulta.getTipoDeConsulta()));

		if (consultasEntreHorarios(consulta.getData(),horarioFinalDaConsulta)
				.size() > 0)
			return false;
		
		
		return true;
	}

	/**
	 * Metodo que retorna uma lista de {@link Consulta} que se encontram entre
	 * os horarios passados
	 * 
	 * @param Calendar horario1
	 *            Primeiro Horario limite
	 * @param Calendar horario2
	 *            Segundo Horario limite
	 * @return List<Consulta> uma lista de consultas que tem seus horarios entre os limites
	 * @throws Exception
	 *             caso algum horario seja <code>null</code>
	 */
	private List<Consulta> consultasEntreHorarios(Calendar horario1,
			Calendar horario2) throws Exception {
		if (horario1 == null || horario2 == null)
			throw new Exception("Operacao invalida: Parametro null");
		List<Consulta> listaEntreHorarios = new ArrayList<Consulta>();
		

		Calendar horarioAnterior = horario1;
		Calendar horarioPosterior = horario2;

		if (horario1.after(horario2)) {
			horarioAnterior = horario2;
			horarioPosterior = horario1;
		}		
		List<Consulta> listaDoDia = listaConsultas(horario1);		
		
		for (Consulta cons : listaDoDia) {
			if ((cons.getData().after(horarioAnterior)
					&& cons.getData().before(horarioPosterior))
					|| cons.getData().equals(horarioAnterior))
				listaEntreHorarios.add(cons);
		}
		return listaEntreHorarios;
	}
	
	/**
	 * Metodo acessador que retorna uma consultaDAO
	 * (consulta do BD).
	 * @return ConsultaDAO
	 */
	public ConsultaDAO getConsultaDAO() {
		return consultaDAO;
	}

}
